/*
 *  Copyright (C) 2011 glauco
 * 
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 * 
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 * 
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.balisunrise.swing.components.documents;

import java.awt.Toolkit;
import java.util.ArrayList;
import java.util.List;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.PlainDocument;

/**
 *
 * @author glauco
 */
public class Document extends PlainDocument{

    /**
     * The System Default Toolkit.
     */
    public static final Toolkit toolkit = Toolkit.getDefaultToolkit();
    
    /**
     * Tamanho maximo de caracteres que o documento aceita
     */
    private int length;

    /**
     * List of listeners.
     */
    private List<DocumentFilter> documentFilters =
            new ArrayList<DocumentFilter>();

    /**
     * Mask of the document.
     */
    private DocumentMask mask;

    /**
     * Contrutor.
     * @param length tamanho máximo para o campo, informe zero para ilimitado.
     */
    public Document(int length) {
        super();
        this.length = length;
    }

    /**
     * Executa um beep.
     */
    public void beep(){
        toolkit.beep();
    }

    public void addDocumentFilter(DocumentFilter filter){
        documentFilters.add(filter);
    }

    public boolean removeDocumentFilter(DocumentFilter filter){
        return documentFilters.remove(filter);
    }

    public DocumentFilter[] getDocumentFilters(){
        return documentFilters.toArray(new DocumentFilter[0]);
    }

    public DocumentMask getMask() {
        return mask;
    }

    public void setMask(DocumentMask mask) {
        this.mask = mask;
    }

    /**
     * Deletes the region of text from <code>offset</code> to
     * <code>offset + length</code>, and replaces it with <code>text</code>.
     * It is up to the implementation as to how this is implemented, some
     * implementations may treat this as two distinct operations: a remove
     * followed by an insert, others may treat the replace as one atomic  
     * operation.
     * 
     * @param offset index of child element
     * @param length length of text to delete, may be 0 indicating don't
     *               delete anything
     * @param text text to insert, <code>null</code> indicates no text to insert
     * @param attrs AttributeSet indicating attributes of inserted text,
     *              <code>null</code>
     *              is legal, and typically treated as an empty attributeset,
     *              but exact interpretation is left to the subclass
     * @exception BadLocationException the given position is not a valid 
     *            position within the document
     * @since 1.4
     */
    @Override
    public void replace(int offset, int length, String text, AttributeSet attrs)
            throws BadLocationException {

        if (length == 0 && (text == null || text.length() == 0)) {
            return;
        }

	writeLock();
	try {
            if (length > 0) {
                remove(offset, length);
            }
            if (text != null && text.length() > 0) {
                insertString(offset, text, attrs);
            }
        } finally {
            writeUnlock();
        }
    }

    /**
     * Insere um segmento de string ao documento.
     * Não aceita valores nulos.
     * Antes de inserir é chamado a função beforeInsert dos filters
     * (DocumentFilter).
     * Por fim é verificado se a nova string não ultrapasse o tamanho maximo
     * de caracteres que este documento pode ter.
     * Caso o segmento de string não seje adicionado ao documento, é disparado
     * um beep
     *
     * Inserts some content into the document.
     * Inserting content causes a write lock to be held while the
     * actual changes are taking place, followed by notification
     * to the observers on the thread that grabbed the write lock.
     * <p>
     * This method is thread safe, although most Swing methods
     * are not. Please see
     * <A HREF="http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html">How
     * to Use Threads</A> for more information.
     *
     * @param offs the starting offset >= 0
     * @param str the string to insert; does nothing with null/empty strings
     * @param a the attributes for the inserted content
     * @exception BadLocationException  the given insert position is not a valid
     *   position within the document
     * @see javax.swing.text.Document#insertString
     * @see DocumentFilter#beforeInsert
     */
    @Override
    public void insertString(int offset, String str, AttributeSet attrs)
                    throws BadLocationException    {

        if (str == null)
            return;

        for (DocumentFilter filters : documentFilters) {
            String value = getContent().getString(0, getLength());
            str = filters.beforeInsert(value,offset,str);
            if(str == null){
                beep();
                return;
            }
        }

        if (mask != null)
            str = mask(offset, str, attrs);

        if (length <= 0){
            super.insertString(offset, str, attrs);
            return;
        }

        if(getLength() == length){
            beep();
            return ;
        }
        
        if (getLength() + str.length() <= length)
            super.insertString(offset, str, attrs);
        else{
            int l = length - getLength();
            super.insertString(offset, str.substring(0, l), attrs);
            beep();
        }
    }

    private String mask(int offset, String str, AttributeSet attrs){
        return str;
    }
}
